""" asterix
"""

from twisted.application import service, internet
from twisted.internet import reactor, protocol, defer, task
from twisted.protocols import basic
from twisted.python import log, logfile, util
from twisted.cred import portal, checkers, credentials

import os, logging, pprint, time, sys, threading, binascii
from starpyex import fastagi, manager
from mylogger import mylogger


mylog = logging.getLogger('asterix')


class asterix:
    """ """

    def __init__(self, **kwargs):
        """ """
        self.ami = {}
        self.ami['username'] = kwargs.get('ami_username', 'username')
        self.ami['secret'] = kwargs.get('ami_secret', 'secret')
        self.ami['server'] = kwargs.get('ami_server', '127.0.0.1')
        self.ami['port'] = kwargs.get('ami_port', 5038)
        self.ami['connect_handler'] = kwargs.get('ami_connect_handler', None)
        self.ami['period'] = kwargs.get('ami_period', 1)
        self.ami['timeout'] = kwargs.get('ami_timeout', 5)
        self.ami['tokens'] = kwargs.get('ami_tokens', 'all')
        self.ami['async'] = kwargs.get('ami_async', False)
        self.ami['events'] = kwargs.get('ami_events', {})

        self.fagi = {}
        self.fagi['interface'] = kwargs.get('fagi_interface', '127.0.0.1')
        self.fagi['port'] = kwargs.get('fagi_port', 4573)
        self.fagi['handler'] = kwargs.get('fagi_handler', None)
        self.fagi['vars'] = kwargs.get('fagi_vars', [])

        self.log = {}
        self.log['file'] = kwargs.get('log_file', 'asterix.log')
        self.log['tokens'] = kwargs.get('log_tokens', 'all')
        self.log['level'] = kwargs.get('log_level', mylogger.DEBUG)
        
        self.m_log = mylogger.mylogger(self.log['file'], self.log['tokens'], self.log['level'])

        f = fastagi.FastAGIFactory(self.fagi_handler)
        reactor.listenTCP(self.fagi['port'], f, 50, self.fagi['interface'])

        self.m_log.debug('log',
                         'fagi vars %s' % self.fagi['vars'])

        self.m_ami = None
        self.ami_is_ok = False

        self.agents = {}
        self.agents_online = {}
        self.agents_free = {}
        self.agents_busy = {}
        self.queues = {}
        self.meetmes = {}
        self.channels = {}

        reactor.callWhenRunning(self.Login)

    def Init(self):
        self.agents = {}
        self.agents_online = {}
        self.agents_free = {}
        self.agents_busy = {}
        self.queues = {}
        self.meetmes = {}
        self.channels = {}

    def isConnected(self):
        return self.ami_is_ok

    def Login(self):

        def onLoginOk( ami ):
            self.ami_is_ok = True
            self.m_log.debug('ami',
                             'ami login ok!!!')
            self.m_ami = ami

            if ('agents' in self.ami['tokens']) or (self.ami['tokens'] == 'all'):
                self.AgentsOnline(True)
            if ('meetme' in self.ami['tokens']) or (self.ami['tokens'] == 'all'):
                self.Meetmes(True)
            if ('channels' in self.ami['tokens']) or (self.ami['tokens'] == 'all'):
                self.Channels(True)

            if self.ami['async']:
                if ('agents' in self.ami['tokens']) or (self.ami['tokens'] == 'all'):
                    self.m_ami.registerEvent('Agentlogin', self.onAgentLogin)
                    self.m_ami.registerEvent('Agentlogoff', self.onAgentLogoff)
                    self.m_ami.registerEvent('Agentcallbacklogin', self.onAgentCallbackLogin)
                    self.m_ami.registerEvent('Agentcallbacklogoff', self.onAgentCallbackLogoff)
                    self.m_ami.registerEvent('AgentCalled', self.onAgentCalled)
                if ('meetme' in self.ami['tokens']) or (self.ami['tokens'] == 'all'):
                    self.m_ami.registerEvent('MeetmeJoin', self.onMeetmeJoin)
                    self.m_ami.registerEvent('MeetmeLeave', self.onMeetmeLeave)
                    self.m_ami.registerEvent('MeetmeMute', self.onMeetmeMute)
                self.m_ami.registerEvent('Status', self.onStatus)
                self.m_ami.registerEvent('Newstate', self.onNewState)
                self.m_ami.registerEvent('ExtensionStatus', self.onExtensionStatus)
                self.m_ami.registerEvent('Newchannel', self.onNewChannel)
                self.m_ami.registerEvent('Hangup', self.onHangup)
            for event, handler in self.ami['events'].items():
                self.m_ami.registerEvent(event, handler)

            reactor.callLater(self.ami['period'], self.ami_period_handler)
            if self.ami['connect_handler']:
                self.ami['connect_handler']()
            return df
        def onLoginErr( reason ):
            self.ami_is_ok = False
            self.m_log.error('ami',
                             'ami login err!!! %s' % reason.getTraceback())
            return reason
        theManager = manager.AMIFactory(self.ami['username'], self.ami['secret'])
        df = theManager.login(self.ami['server'], self.ami['port'], self.ami['timeout'])
        df.addCallbacks(onLoginOk, onLoginErr)
        self.m_log.debug('ami',
                         'ami login start')
        reactor.callLater(self.ami['timeout'], self.ami_wdt_handler)

    def onCdr(self, ami, event):
        """ onCdr """
        self.m_log.debug('ami',
                         'event: Status: %s' % event)

    def onStatus(self, ami, event):
        """ onStatus """
        self.m_log.debug('ami',
                         'event: Status: %s' % event)

    def onExtensionStatus(self, ami, event):
        """ onExtensionStatus """
        self.m_log.debug('ami',
                         'event: ExtensionStatus: %s' % event)

    def onNewState(self, ami, event):
        """ onNewState """
        self.m_log.debug('ami',
                         'event: Newstate: %s' % event)

    def onMeetmeJoin(self, ami, event):
        """ onMeetmeJoin """
        self.m_log.debug('ami',
                         'event: MeetmeJoin: %s' % event)
        meetme = self.meetmes.get(event['meetme'], {})
        users = meetme.get('users', {})
        user = {}
        user['confno'] = event['meetme']
        user['userno'] = event['usernum']
        user['callerid'] = event.get('calleridnum', None)
        user['callername'] = event.get('calleridname', None)
        user['channel'] = event['channel']
        user['uniqueid'] = event['uniqueid']
        user['status'] = ''
        users[event['usernum']] = user
        meetme['users'] = users
        self.meetmes[event['meetme']] = meetme

    def onMeetmeLeave(self, ami, event):
        """ onMeetmeLeave """
        self.m_log.debug('ami',
                         'event: MeetmeLeave: %s' % event)
        try:
            del self.meetmes[event['meetme']]['users'][event['usernum']]
            if not len(self.meetmes[event['meetme']]['users']):
                del self.meetmes[event['meetme']]
        except:
            self.m_log.error('ami',
                             'meetme %s userno %s is not correct' % (event['meetme'], event['usernum']))
            pass

    def onMeetmeMute(self, ami, event):
        """ onMeetmeMute """
        self.m_log.debug('ami',
                         'event: MeetmeMute: %s' % event)
        try:
            if event['status'] == 'on':
                self.meetmes[event['meetme']]['users'][event['usernum']]['status'] = 'mute'
            else:
                self.meetmes[event['meetme']]['users'][event['usernum']]['status'] = ''
        except:
            self.m_log.error('ami',
                             'meetme %s userno %s is not correct' % (event['meetme'], event['usernum']))
            pass

    def Meetmes(self, refresh = False):
        """ Meetmes """
        def onMeetmeErr( reason ):
            self.m_log.error('ami',
                             'meetme err %s' % reason.getTraceback())
            return df_ret.errback(reason)
        def onMeetmeOk( result ):
            def meetmeList():
                def onMeetmeListErr( reason ):
                    self.m_log.error('ami',
                                     'meetme list err\n%s' % reason.getTraceback())
                    meetmes[confno] = {}
                    meetmeList()
                def onMeetmeListOk( result ):
                    self.m_log.debug('ami',
                                     'meetme list ok')
                    meetmes[confno] = {}  # confno
                    users = meetmes[confno]['users'] = {}
                    for user_str in result:
                        channel_parsing_list = user_str.split('!')
                        user = {}
                        user['channel_string'] = user_str
                        user['confno'] = confno
                        user['userno'] = channel_parsing_list[0]
                        user['callerid'] = channel_parsing_list[1]
                        user['callername'] = channel_parsing_list[2]
                        user['channel'] = channel_parsing_list[3]
                        if channel_parsing_list[6]:
                            user['status'] = 'mute'
                        else:
                            user['status'] = ''
                        users[user['userno']] = user
                    meetmeList()

                if not len(meetmeno_list):
                    self.meetmes = meetmes
                    self.m_log.debug('ami',
                                     'meetmes: %s' % (meetmes))
                    return df_ret.callback(meetmes)
                else:
                    confno = str(meetmeno_list.pop(0)) # confno
                    df = self.m_ami.command('meetme list %s concise' % confno)
                    df.addCallbacks( onMeetmeListOk, onMeetmeListErr )
                    return df

            self.m_log.debug('ami',
                             'meetme result %s' % result)
            if (len(result) <= 2):
                self.meetmes = meetmes
                return df_ret.callback(meetmes)
            for conference in result[1:-1]:
                confno = conference.split(' ')[0]
                self.m_log.debug('ami',
                                 'meetme %s add to list' % (confno))
                meetmeno_list.append(confno)
            meetmeList()

        if not refresh:
            return self.meetmes

        meetmes = {}
        meetmeno_list = []
        df_ret = defer.Deferred()
        df = self.m_ami.command('meetme')
        df.addCallbacks( onMeetmeOk, onMeetmeErr )
        return df_ret


    def Queues(self, refresh = False):
        """ Queues """
        def onQueueShowErr( reason ):
            self.m_log.error('ami',
                             'queue show err %s' % reason.getTraceback())
            return df_ret.errback(reason)
        def onQueueShowOk( result ):
            self.m_log.debug('ami',
                             'queue show ok')
            qname = ''; members_free = []; members_busy = []; members_off = []; callers = []
            for queue_str in result:
                if queue_str and not queue_str[0].isspace():
                    if qname:
                        queues[qname] = {}
                        queues[qname]['members_free'] = members_free
                        queues[qname]['members_busy'] = members_busy
                        queues[qname]['members_off'] = members_off
                        queues[qname]['callers'] = callers
                        self.m_log.debug('ami',
                                         'queue %s\n' % qname +\
                                         'members_free %s\n' % queues[qname]['members_free'] +\
                                         'members_busy %s\n' % queues[qname]['members_busy'] +\
                                         'members_off %s\n' % queues[qname]['members_off'] +\
                                         'callers %s' % queues[qname]['callers'])
                    qname = queue_str.split(' ')[0]
                    members_free = []; members_busy = []; members_off = []; callers = []
                elif queue_str.find('Members') != -1:
                    list_ptr = 'members'
                elif queue_str.find('Callers') != -1:
                    list_ptr = 'callers'
                else:
                    queue_str_strip = queue_str.strip()
                    queue_str_list = queue_str_strip.split(' ')
                    if list_ptr == 'callers':
                        callers.append(queue_str_list[1])
                    else:
                        if queue_str_strip.find('Unavailable') != -1:
                            members_off.append(queue_str_list[0])
                            #try:
                            #    del(self.agents_free[queue_str_list[0]])
                            #except:
                            #    pass
                            #try:
                            #    del(self.agents_busy[queue_str_list[0]])
                            #except:
                            #    pass
                        elif queue_str_strip.find('Not in use') != -1:
                            members_free.append(queue_str_list[0])
                            #self.agents_free[queue_str_list[0]] = True
                            #try:
                            #    del(self.agents_busy[queue_str_list[0]])
                            #except:
                            #    pass
                        else:
                            members_busy.append(queue_str_list[0])
                            #self.agents_busy[queue_str_list[0]] = True
                            #try:
                            #    del(self.agents_free[queue_str_list[0]])
                            #except:
                            #    pass
            if qname:
                queues[qname] = {}
                queues[qname]['members_free'] = members_free
                queues[qname]['members_busy'] = members_busy
                queues[qname]['members_off'] = members_off
                queues[qname]['callers'] = callers
                self.m_log.debug('ami',
                                 'queue %s\n' % qname +\
                                 'members_free %s\n' % queues[qname]['members_free'] +\
                                 'members_busy %s\n' % queues[qname]['members_busy'] +\
                                 'members_off %s\n' % queues[qname]['members_off'] +\
                                 'callers %s' % queues[qname]['callers'])
            self.queues = queues
            return df_ret.callback(queues)

        if not refresh:
            return self.queues

        queues = {}
        df_ret = defer.Deferred()
        df = self.m_ami.command('queue show')
        df.addCallbacks( onQueueShowOk, onQueueShowErr )
        return df_ret


    def onAgentLogin(self, ami, event):
        """ onAgentLogin """
        self.m_log.debug('ami',
                         'event: Agentlogin: %s' % event)
        #agent = {}
        #agent['exten'] = event['agent']
        #agent['channel'] = event['channel']
        #agent['uniqueid'] = event['uniqueid']
        #self.agents_online[event['agent']] = agent
        agent = {}
        agent['exten'] = event['agent']
        agent['channel'] = event.get('channel', None)
        agent['bridged'] = None
        self.agents_online[event['agent']] = agent

    def onAgentLogoff(self, ami, event):
        """ onAgentLogoff """
        self.m_log.debug('ami',
                         'event: Agentlogoff: %s' % event)
        try:
            del self.agents_online[event['agent']]
        except:
            pass

    def onAgentCallbackLogin(self, ami, event):
        """ onAgentCallbackLogin """
        self.m_log.debug('ami',
                         'event: Agentcallbacklogin: %s' % event)

    def onAgentCallbackLogoff(self, ami, event):
        """ onAgentCallbackLogoff """
        self.m_log.debug('ami',
                         'event: Agentcallbacklogoff: %s' % event)

    def onAgentCalled(self, ami, event):
        """ onAgentCalled """
        self.m_log.debug('ami',
                         'event: AgentCalled: %s' % event)

    def AgentsFree(self):
        """ AgentsFree """
        if not self.ami_is_ok:
            return None
        return self.agents_free

    def AgentsBusy(self):
        """ AgentsBusy """
        if not self.ami_is_ok:
            return None
        return self.agents_busy

    def AgentsOnline(self, refresh = False):
        """ AgentsOnline """
        def onAgentShowOnlineErr( reason ):
            self.m_log.error('ami',
                             'agent show online err %s' % reason.getTraceback())
            return df_ret.errback(reason)
        def onAgentShowOnlineOk( result ):
            self.m_log.debug('ami',
                             'agent show online result %s' % result)
            if (len(result) <= 1):
                self.agents_online = {}
                self.agents_free = {}
                self.agents_busy = {}
                return df_ret.callback(agents)
            for agent_str in result[0:-1]:
                self.m_log.debug('ami',
                                 'agent_str %s' % agent_str)
                #agent_list = agent_str.split(' ', 1)
                #exten = agent_list[0]
                #channel = agent_list[1].strip().split(' ')[4]
                try:
                    exten = agent_str.split(' ', 1)[0]
                except:
                    exten = None
                try:
                    channel = agent_str.split('logged in on ')[1].split(' ')[0]
                except:
                    channel = None
                try:
                    bridged = agent_str.split('talking to ')[1].split(' ')[0]
                except:
                    bridged = None
                if not exten:
                    continue
                agent = {}
                agent['exten'] = exten
                agent['channel'] = channel
                agent['bridged'] = bridged
                if bridged:
                    agent['status'] = 'busy'
                    agents_busy[exten] = agent
                else:
                    agent['status'] = 'free'
                    agents_free[exten] = agent
                agents[exten] = agent
                self.m_log.debug('ami',
                                 'agent %s' % agent)
            self.agents_online = agents
            self.agents_free = agents_free
            self.agents_busy = agents_busy
            return df_ret.callback(agents)

        if not refresh:
            return self.agents_online

        agents = {}
        agents_free = {}
        agents_busy = {}
        df_ret = defer.Deferred()
        df = self.m_ami.command('agent show online')
        df.addCallbacks( onAgentShowOnlineOk, onAgentShowOnlineErr )
        return df_ret

    def onNewChannel(self, ami, event):
        """ onNewChannel """
        self.m_log.debug('ami',
                         'event: NewChannel: %s' % event)
        channel = {}
        channel['channel'] = event['channel']
        channel['context'] = None
        channel['exten'] = None
        channel['priority'] = None
        channel['stats'] = event['state']
        channel['application'] = None
        channel['data'] = None
        channel['callerid'] = event['calleridnum']
        channel['accountcode'] = event['calleridname']
        channel['amaflags'] = None
        channel['duration'] = None
        channel['bridged'] = None
        self.channels[event['channel']] = channel


    def onHangup(self, ami, event):
        """ onHangup """
        self.m_log.debug('ami',
                         'event: Hangup: %s' % event)
        try:
            del self.channels[event['channel']]
        except:
            pass

    def Channels(self, refresh = False):
        """ Channels """
        def onShowChannelsErr( reason ):
            self.m_log.error('ami',
                             'show channels err %s' % reason.getTraceback())
            return df_ret.errback(reason)
        def onShowChannelsOk( result ):
            self.m_log.debug('ami',
                             'show channels ok')
            for channel_str in result:
                self.m_log.debug('ami',
                                 'channel_str: %s' % channel_str)

            # for asterisk 1.4
                #Channel:Context:Exten:Priority:Stats:Application:Data:CallerID:Accountcode:Amaflags:Duration:Bridged
                channel_list = channel_str.split('!')
            # for asterisk 1.2
                #channel_list = channel_str.split(':')

            # for asterisk 1.4
                channel = channel_list[0]
                channels[channel] = {}
                channels[channel]['concise'] = channel_str
                channels[channel]['channel'] = channel_list[0]
                channels[channel]['context'] = channel_list[1]
                channels[channel]['exten'] = channel_list[2]
                channels[channel]['priority'] = channel_list[3]
                channels[channel]['stats'] = channel_list[4]
                channels[channel]['application'] = channel_list[5]
                channels[channel]['data'] = channel_list[6]
                channels[channel]['callerid'] = channel_list[7]
                channels[channel]['accountcode'] = channel_list[8]
                channels[channel]['amaflags'] = channel_list[9]
                channels[channel]['duration'] = channel_list[10]
                channels[channel]['bridged'] = channel_list[11]
            # for asterisk 1.2
                #channel = channel_list[0] + '@' + channel_list[1]
                #channel = channel_list[0] + ':' + channel_list[1]# + '@' + channel_list[2]
                #caller_id = channel_list[8]

            self.channels = channels
            return df_ret.callback(channels)

        if not refresh:
            return self.channels

        channels = {}
        df_ret = defer.Deferred()
    # for asterisk 1.4
        df = self.m_ami.command('core show channels concise')
    # for asterisk 1.2
        #df = self.m_ami.command('show channels concise')
        df.addCallbacks( onShowChannelsOk, onShowChannelsErr )
        return df_ret


    def ami_wdt_handler(self):
        """ ami_wdt_handler """
        if self.ami_is_ok:
            self.ami_is_ok = False
        else:
            self.Init()
            self.Login()
            return
        reactor.callLater(self.ami['timeout'], self.ami_wdt_handler)

    def ami_period_handler( self ):
        """ ami_period_handler """

        def onPingErr( reason ):
            self.m_log.error('ami',
                             'ping err %s' % reason.getTraceback())
            self.ami_is_ok = False
            reactor.callLater(self.ami['period'], self.ami_period_handler)

        def onPingOk( result ):
            self.m_log.debug('ami',
                             'ping ok %s' % result)
            self.ami_is_ok = True
            reactor.callLater(self.ami['period'], self.ami_period_handler)

        if not self.ami['async'] and (('channels' in self.ami['tokens']) or (self.ami['tokens'] == 'all')):
            self.Channels(True)
        if not self.ami['async'] and (('agents' in self.ami['tokens']) or (self.ami['tokens'] == 'all')):
            self.AgentsOnline(True)
        if not self.ami['async'] and (('meetme' in self.ami['tokens']) or (self.ami['tokens'] == 'all')):
            self.Meetmes(True)
        df = self.m_ami.ping()
        df.addCallbacks(onPingOk, onPingErr)
        return df

    def fagi_handler(self, agi):
        """ fagi_handler """
        self.m_log.debug('fagi',
                         'fagi_handler')
        def onGetVariablesOk(result):
            """ onGetVariablesOk """
            self.m_log.debug('fagi',
                             'onGetVariablesOk')
            if not self.fagi['handler']:
                agi.finish()
                return
            for var_name, var_val in result.items():
                #self.m_log.debug('fagi',
                #                 'var %s = %s' % (var_name, var_val))
                agi.variables[var_name] = var_val
            self.m_log.debug('fagi',
                             'get vars: %s' % result)
            ret_vars = self.fagi['handler'](agi)
            if type(ret_vars) != dict:
                return
            self.m_log.debug('fagi',
                             'set vars: %s' % ret_vars)
            df = agi.setVariables(**ret_vars)
            df.addCallbacks(onSetVariablesOk)
            return df
        def onSetVariablesOk(result):
            """ onSetVariablesOk """
            self.m_log.debug('fagi',
                             'onSetVariablesOk')
            for var_name, var_val in result.items():
                self.m_log.debug('fagi',
                                 'var %s = %s' % (var_name, var_val))
            agi.finish()

        df = agi.getVariables(*self.fagi['vars'])
        df.addCallback(onGetVariablesOk)
        return df
